/**
 * Created by hdr on 17/11/2.
 */

const UUID_IBT_SERVICES = "0000FFE0-0000-1000-8000-00805F9B34FB"
const UUID_IBT_READ = "0000FFE1-0000-1000-8000-00805F9B34FB"
const UUID_IBT_WRITE = "0000FFE3-0000-1000-8000-00805F9B34FB"

const UUID_IBT_SERVICES_1 = "0000FFF0-0000-1000-8000-00805F9B34FB"
const UUID_IBT_READ_1 = "0000FFF1-0000-1000-8000-00805F9B34FB"
const UUID_IBT_WRITE_1 = "0000FFF2-0000-1000-8000-00805F9B34FB"

const SECOND_2000_YEAR = 946656000;
const promise = require('./wx_promise')

const MeasureStore = require('../store/measureStore')
const moment = require('../libs/moment')
const BodyfatReport = require('../report/Bodyfat')
const BmiReport = require('../report/BMI')
const theme = require("../theme.config.js")

class decoder {

  constructor(listener, bodyParams, appParams) {
    this.listener = listener

    wx.onBluetoothDeviceFound(this.onDeviceScan.bind(this))
    wx.onBLEConnectionStateChange(this.onDeviceConnectStateChange.bind(this))
    wx.onBLECharacteristicValueChange(this.onDeviceValueChange.bind(this))
    wx.onBluetoothAdapterStateChange(this.onBluetoothAdapterStateChange.bind(this))
    this.bodyParams = bodyParams
    this.appid = appParams.appid
    this.secret = appParams.secret
    this.version = appParams.version
    this.overtime = appParams.overtime
  }

  weightScale = 100

  device = null

  isScale1 = false

  //收到上一次非稳定重量的时间，用以判断是否有测阻抗过程
  lastWeightTime = 0

  cmdString
  bleState = {
    available: false,
    discovering: false
  }

  heartRate = null

  scaleData = []//存储秤数据

  onBluetoothAdapterStateChange(res) {
    if (!this.bleState.available && res.available && !res.discovering) {
      //检测到开启蓝牙，启动扫描
      console.log("检测到开启蓝牙，启动扫描")
      this.bodyParams && this.startScan()
    } else {
      if (!res.available) {
        this.listener && this.listener.onBleDisable()
        this.device = null
      }
    }
    this.bleState = res

  }

  get isScanning() {
    return this.bleState && this.bleState.discovering
  }

  //扫描发现了设备
  onDeviceScan(deviceObj) {
    const device = deviceObj.devices[0]
    console.log("扫描到设备",deviceObj.length, device)
    if ((device.name !== 'QN-Scale' && device.name !== "QN-Scale1") || device.RSSI === 127 || device.RSSI < -90) {
      return
    }
    const {
      advertisData
    } = device
    if (advertisData == null) {
      return
    }
    const advertisArrayData = new Uint8Array(advertisData)
    const advertisDataString = this.arrayToHexString(advertisArrayData)

    if (!advertisArrayData || advertisDataString.length === 0) {
      return
    }
    const hasScreenOn = advertisArrayData[5] !== 1
    if (!hasScreenOn) {
      //过滤没有开机的设备
      return
    }

    let mac = ""
    const lastIndex = advertisArrayData.length - 1
    for (let i = lastIndex; i > lastIndex - 6; i--) {
      let item = advertisArrayData[i]
      if (i !== lastIndex) {
        mac += ":"
      }
      let s = item.toString(16).toUpperCase()
      if (s.length === 1) {
        s = '0' + s
      }
      mac += s
    }
    /*
    if (mac != "FA:E5:83:E4:07:1F") {
      return
    }
    */

    this.listener.onScanDevice({
      mac,
      ...device,
      localName: device.name,
      manufacturer: advertisDataString,
      advertisArrayData
    })
  }

  onDeviceConnectStateChange({
    deviceId,
    connected
  }) {
    if (connected) {

    } else {
      
      promise(wx.closeBLEConnection)({
        deviceId
      })
      this.device = null
    }
    
    this.listener && this.listener.onConnectStateChange(deviceId, connected)

  }

  onGetDeviceServices(res, deviceId) {
    var targetUUID
    res.services.forEach((service) => {
      if (service.uuid === UUID_IBT_SERVICES) {
        targetUUID = UUID_IBT_SERVICES
        this.isScale1 = false
      } else if (service.uuid === UUID_IBT_SERVICES_1) {
        targetUUID = UUID_IBT_SERVICES_1
        this.isScale1 = true
      }
    })
    // console.log("准备发现发现服务", this.isScale1, targetUUID)
    return promise(wx.getBLEDeviceCharacteristics)({
        deviceId: deviceId,
        serviceId: targetUUID,
      })
      .then(res => this.onGetDeviceCharacteristics(deviceId))
      .catch(err => {
        return this.handleConnectionError()
      })
  }

  onGetDeviceCharacteristics(deviceId) {
    var serviceId, characteristicId
    if (!this.isScale1) {
      serviceId = UUID_IBT_SERVICES
      characteristicId = UUID_IBT_READ
    } else {
      serviceId = UUID_IBT_SERVICES_1
      characteristicId = UUID_IBT_READ_1
    }
    console.log("准备发现特征值 decoder", deviceId, serviceId, characteristicId)
    return promise(wx.notifyBLECharacteristicValueChange)({
        deviceId,
        serviceId,
        characteristicId,
        state: true,
      })
      .catch(err => {
        console.error("启动通知错误", err)
        return this.handleConnectionError()
      })
  }

  onDeviceValueChange({
    deviceId,
    value
  }) {
    if (this.device == null) {
      return promise(wx.closeBLEConnection)({
        deviceId
      })
    }
    const data = new Uint8Array(value);
    this.decode(data)
  }

  handleConnectionError() {
    if (this.device == null) {
      return this.startScan()
    }
    return promise(wx.closeBLEConnection)({
      deviceId: this.device.deviceId
    })
      .then(() => {
        return this.startScan()
      })
      .catch(e => {
        console.error("重新启动扫描错误", e)
        this.startScan()
      })
  }

  startScan() {
    return promise(wx.openBluetoothAdapter)()
      .then((e) => {
        if (this.bleState && this.bleState.disovering) {
          return this.stopScan()
        } else {
          return true
        }
      })
      .catch(() => {})
      .then(() => promise(wx.startBluetoothDevicesDiscovery)({
          allowDuplicatesKey: true,
          services: ["FFE0"],
        })
        .then(() => {
          this.listener && this.listener.onStartScan()
        })
        .catch(e => {
          if (e.errCode == 10001) {
            this.listener && this.listener.onBleDisable()
          }
          throw e
        })

      )
  }

  stopScan() {
    return promise(wx.stopBluetoothDevicesDiscovery)()
      .then(() => {
        return this.listener && this.listener.onStopScan()
      })
  }

  restartScan() {
    return promise(wx.openBluetoothAdapter)()
      .then(() => {
        if (this.device != null) {
          return promise(wx.closeBLEConnection)({
            deviceId: this.device.deviceId
          })
          .catch((e) => {})
          .then(() => {
            this.device = null
            return this.startScan()
          })
        } else {
          return this.startScan()
        }
      })

  }

  stop() {
    if (this.device != null) {
      return promise(wx.closeBLEConnection)({
        deviceId: this.device.deviceId
      })
      .catch((e) => {
        console.log("关闭连接出错")
        return Promise.reject(e)
      })
    } else {
      return this.stopScan()
    }
  }

  connectDevice(device) {
    if (this.device){
      return
    }
    this.device = device
    const {
      deviceId
    } = device
    let _moment = moment()
    console.log(`连接蓝牙: ${_moment.format("YYYY-MM-DD H:mm:ss")} (${_moment.valueOf()})`, device)
    let connectObj = {
      deviceId
    }
    if (this.overtime != undefined) {
      connectObj.timeout = this.overtime
    }
    let timestamp = +new Date

    return promise(wx.createBLEConnection)(connectObj)
      .then(res => {
        return promise(wx.getBLEDeviceServices)({
          deviceId
        })
      })
      .then(res => {
        return this.onGetDeviceServices(res, deviceId)
      })
      .then(res => {
        let _moment = moment()
        console.log("蓝牙连接成功耗时 success:", +new Date - timestamp + "ms")
        console.log(`连接设备成功: ${_moment.format("YYYY-MM-DD H:mm:ss")} (${_moment.valueOf()})`, res)
      })
      .catch((err) => {
        let _moment = moment()
        console.log("蓝牙连接失败耗时 err:", +new Date - timestamp + "ms")
        console.error(`连接蓝牙出错: ${_moment.format("YYYY-MM-DD H:mm:ss")} (${_moment.valueOf()})`, err)
        this.device = null
        //return this.handleConnectionError()
        this.listener && this.listener.bleConnectErr(err)
      })
  }

  decode(data) {
    const scaleType = data[2];
    const cmd = this.arrayToHexString(data)
    console.log('收到数据：', cmd)

    switch (data[0]) {
      case 0x12:
        {
          if (this.ishearRate(scaleType)) {
            this.heartRate = (data[10] & 0x01) === 1 ? true : false
          } else {
            this.heartRate = false
          }
          this.cmdString = cmd

          this.weightScale = (data[10] & 0x01) === 1 ? 100 : 10
          this.lastWeightTime = 0

          const sendCmd = this.buildCmd(0x13, scaleType, 0x01, 0x10, 0x00, 0x00, 0x00);
          this.writeData(sendCmd)
          break;
        }
      case 0x10:
        {
          const weight = this.decodeWeight(data[3], data[4]);
          let resistance = this.decodeImpedance(data[6], data[7])
          let resistance500 = this.decodeImpedance(data[8], data[9])
          let heart_rate = 0

          if (this.heartRate) {
            heart_rate = data[10] == 0 ? 0 : +("0x" + this.arrayToHexString([data[10]]))
          }

          let manufacturer = this.arrayToHexString([this.device.advertisArrayData[2]]) + this.arrayToHexString([this.device.advertisArrayData[3]])
          const scaleParam = {
            mac: this.device.mac,
            scale_weight: weight,
            scale_resistance: resistance,
            scale_resistance500: resistance500,
            heart_rate,
            manufacturer,
            app_type: theme.app_type,
          }
          if (scaleType == 0xff) {//心率双模秤
            let left_weight = this.decodeWeight(data[11], data[12])

            Object.assign(scaleParam, {
              left_weight,
              right_weight: this.accuracy(weight, left_weight)
            })
          }
          if (data[5] === 0) {
            this.lastWeightTime = weight == 0 ? 0 : new Date().getTime()
            //this.listener && this.listener.unitDispose("kg", 1)
            this.listener && this.listener.onGetRealTimeWeight(weight)
          } else if (data[5] === 1) {
            const scaleString = this.arrayToHexString(data) + this.cmdString

            this.listener && this.listener.onStartFetch(this.device)
            //if (this.isHeartRateScale(scaleType)) {
            if (this.ishearRate(scaleType)) {
              if (resistance != 0 && resistance500 != 0) {
                scaleParam.device_type = 1
              }
              //心率秤，计算指标
              /*  this.fetchMeasure({
                   scaleString
                 }) */
              this.uploadFetchMeasure({
                scaleParam
              })
                .then(measure => {
                  // console.log("下发数据为：", measure)
                  const {
                    bodyfat,
                    bmi
                  } = measure
                  // const bodyfat = 25;
                  // const bmi = 21;
                  const bodyfatItem = BodyfatReport.build(measure)
                  const bmiItem = BmiReport.build(measure)


                  const bodyfatLevel = bodyfatItem ? bodyfatItem.level + 1 : 1;
                  const bmiLevel = bmiItem ? bmiItem.level + 1 : 1;

                  const _bodyfat1 = (bodyfat * 10) >> 8
                  const _bodyfat2 = (bodyfat * 10) & 0xFF

                  const _bmi1 = (bmi * 10) >> 8
                  const _bmi2 = (bmi * 10) & 0xFF

                  const sendCmd = this.buildCmd(0x1f, scaleType, 0x10, _bodyfat1, _bodyfat2, bodyfatLevel, _bmi1, _bmi2, bmiLevel)
                  this.writeData(sendCmd)
                })
            } else {
              //非心率秤，下发结束命令
              const sendCmd = this.buildCmd(0x1f, scaleType, 0x10);
              this.writeData(sendCmd)

              const testTime = new Date().getTime() - this.lastWeightTime
              const hasResistance = testTime > 4000
              //在这判断是否需要
              console.log("测量阻抗时间", testTime)
              /* this.listener && this.listener.onGetSteadyData({
                scaleString,
                weight,
                hasResistance
              }) */
              this.listener && this.listener.onSteadyData({
                scaleParam,
                weight,
                hasResistance
              })
            }
          } else if (data[5] === 2) {
            const sendCmd = this.buildCmd(0x1f, scaleType, 0x10);
            this.writeData(sendCmd)

            const scaleString = this.arrayToHexString(data) + this.cmdString
            /* this.fetchMeasure({
              scaleString,
              notify: true
            }) */
            if (scaleParam.device_type) {
              delete scaleParam.device_type
            }
            this.uploadFetchMeasure({
              scaleParam,
              notify: true
            })
          }
          break;
        }
      case 0x14:
        {
          if (!this.isScale1) {
            return
          }
          const differ = moment().unix() - SECOND_2000_YEAR;
          var timeData = [];
          for (var i = 0; i < 4; i++) {
            timeData.push(differ >> (i * 8));
          }
          this.writeData(this.buildCmd(0x20, scaleType, timeData[0], timeData[1], timeData[2], timeData[3]))
          break;
        }
    }
  }

  accuracy(v1, v2) {//为防止小数计算，精度丢失
    let n = 10000

    return (v1 * n - v2 * n) / n
  }
  ishearRate(scaleType) {
    return scaleType === 0x21 || scaleType === 0xff
  }
  isHeartRateScale(scaleType) {
    return scaleType === 0x21
  }

  decodeWeight(a1, b1) {

    const d = (a1 << 8) + b1;
    let weight = d / this.weightScale;
    while (weight > 250) {
      weight = weight / 10;
    }
    return weight;
  }
  
  decodeImpedance(a1, b1) {
    return (a1 << 8) + b1
  }

  arrayToHexString(data) {
    let str = "";

    data.forEach((item) => {
      const s = item.toString(16)
      if (s.length > 1) {
        str += s
      } else {
        str += '0' + s
      }
    });
    return str
  }

  uploadScaleData({
    scaleParam,
    hasResistance,
    hasPopup,
    invalid_id,
  }) {
    return this.uploadFetchMeasure({
      scaleParam,
      hasResistance,
      notify: true,
      hasPopup,
      invalid_id,
    })
  }

  uploadFetchMeasure({
    scaleParam,
    hasResistance,
    notify,
    hasPopup,
    invalid_id
  }) {
    let params = {
      device: this.device,
      scaleParam,
      bodyParams: this.bodyParams,
      hasResistance,
      needPrepare: notify,
      invalid_id,
      APP_ID: this.appid,
      SECRET: this.secret,
      version: this.version
    }
    return MeasureStore.uploadScaleData(params)
      .then(data => {
        if (notify) {
          this.listener.onGetData(this.device, data, this.scaleData)
        } else {
          return data
        }
      })
      .catch(data => {
        const {
          code,
          invalid_id
        } = data
        if (data.code == "602") {
          this.listener && this.listener.onSteadyData({
            scaleParam,
            weight: null,
            hasResistance,
            invalid_id
          })
        } else if (data.errMsg == "request:fail timeout" || data.errMsg == "request:fail 请求超时。") {
          wx.showToast({
            icon: "none",
            title: '网络不稳定,请求超时'
          })
        } else {
          this.listener && this.listener.onUnusual(data)
        }
        
      })
  }

  doBuildData({
    scaleString,
    hasResistance,
    hasPopup,
    invalid_id,
  }) {
    return this.fetchMeasure({
      scaleString,
      hasResistance,
      notify: true,
      hasPopup,
      invalid_id,
    })
  }

  //scaleString：称重的字符串，用来解析重量和阻抗值
  //hasResistance：是否有测量阻抗的过程
  //notify：请求成功后，是否更新测量界面的数据
  //hasPopup：是否有弹过窗
  //invalid_id：无效数据的id，阻抗相差过大时，服务器返回的，用户点击确定后，再把这个数据传回给服务器
  fetchMeasure({
    scaleString,
    hasResistance,
    notify,
    hasPopup,
    invalid_id
  }) {
    const params = {
      device: this.device,
      scaleString,
      bodyParams: this.bodyParams,
      hasResistance,
      needPrepare: notify,
      invalid_id,
      APP_ID: this.appid,
      SECRET: this.secret,
      version: this.version
    }
    return MeasureStore.uploadData(params)
      .then(data => { 
        if (notify) {
          this.listener.onGetData(this.device, data, this.scaleData)
        } else {
          return data
        }
      })
      .catch(data => {
        const {
          code,
          invalid_id
        } = data
        if (code == "602") {
          this.listener && this.listener.onGetSteadyData({
            scaleString,
            weight: null,
            hasResistance,
            invalid_id
          })
        }
        if (code == "400") {
          this.listener && this.listener.onInvalidError(data)
        }
      })
  }

  buildCmd(cmd, deviceType, ...data) {
    const cmdData = [cmd, data.length + 4, deviceType];
    let checksum = 0;
    cmdData.push(...data);
    cmdData.forEach((item) => checksum += item);
    checksum = checksum & 0xFF;
    cmdData.push(checksum);

    let str = "写入数据: " + this.arrayToHexString(cmdData)

    console.log(str);

    return cmdData;

  }

  writeData(cmd) {
    const ab = new ArrayBuffer(cmd.length)
    const dv = new DataView(ab);
    cmd.forEach((value, index) => {
      dv.setUint8(index, value)
    })
    var serviceId, characteristicId

    if (!this.isScale1) {
      serviceId = UUID_IBT_SERVICES
      characteristicId = UUID_IBT_WRITE
    } else {
      serviceId = UUID_IBT_SERVICES_1
      characteristicId = UUID_IBT_WRITE_1
    }
    return promise(wx.writeBLECharacteristicValue)({
      deviceId: this.device.deviceId,
      serviceId,
      characteristicId,
      value: ab,
    })
  }

  formatCmdData(cmd) {
    let length = cmd.length    
    let index = 0    
    let len = 2
    let tmp = []

    while (index < length) {
      tmp.push(cmd.substr(index, len))
      index += len
    }

    return tmp
  }
}

module.exports = decoder